import cv2, sys, numpy as np

from PyQt4 import QtCore
from PyQt4 import Qt
from PyQt4 import QtGui
from PyQt4 import QtOpenGL

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *


class CameraDevice(QtCore.QObject):
    
    _DEFAULT_FPS = 30
    
    newFrame = QtCore.pyqtSignal(np.ndarray)
    
    def __init__(self, cameraId=0, mirrored=False, parent=None):
        super(CameraDevice, self).__init__(parent)
        
        self.mirrored = mirrored
        
        self._cameraDevice = cv2.VideoCapture(cameraId)
        
        self._timer = QtCore.QTimer(self)
        self._timer.timeout.connect(self._queryFrame)
        self._timer.setInterval(1000/self.fps)
        
        self.paused = False
    
    @QtCore.pyqtSlot()
    def _queryFrame(self):
        success, frame = self._cameraDevice.read()
        if self.mirrored:
            frame = cv2.flip(frame, 1)
        self.newFrame.emit(frame)

    @property
    def paused(self):
        return not self._timer.isActive()
    
    @paused.setter
    def paused(self, p):
        if p:
            self._timer.stop()
        else:
            self._timer.start()
    
    @property
    def frameSize(self):
        w = self._cameraDevice.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)
        h = self._cameraDevice.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)
        return int(w), int(h)
    
    @property
    def fps(self):
        _fps = self._cameraDevice.get(cv2.cv.CV_CAP_PROP_FPS)
        if not _fps > 0:
            _fps = self._DEFAULT_FPS
        return _fps



class ARWidget(QtOpenGL.QGLWidget):
    
    newFrame = QtCore.pyqtSignal(np.ndarray)
    
    def __init__(self, cameraDevice, parent=None):
        super(ARWidget, self).__init__(parent)
        
        self._frame = None

        self._pose = np.eye(4, dtype = np.float64)
        
        self._cameraDevice = cameraDevice
        self._cameraDevice.newFrame.connect(self._onNewFrame)
        
        w, h = self._cameraDevice.frameSize

        if not w*h:
            w = 640
            h = 480
            raise ValueError("Incorrect image size! (An error seems to have occured with the video device)")
        
        self.setMinimumSize(w, h)
        self.setMaximumSize(w, h)

    def initializeGL(self):
        glViewport(0, 0, self.width(), self.height());
        glClearColor(1.0, 0.5, 0.0, 1.0)
        glClearDepth(1.0)
        glPolygonMode( GL_FRONT_AND_BACK, GL_FILL )
        glEnable(GL_NORMALIZE);
        glEnable(GL_DEPTH_TEST);
        glShadeModel(GL_SMOOTH);
        glDepthMask(GL_TRUE);
        glDepthFunc(GL_LEQUAL);
        glEnable(GL_LIGHT0);
        glLineWidth(3.0)


    def paintGL(self):
        if self._frame is None:
            return
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        self.drawFrame()
        #self.draw3DScene()

    def resizeGL(self, w, h):
        pass
    
    @QtCore.pyqtSlot(np.ndarray)
    def _onNewFrame(self, frame):
        self._frame = np.copy(frame)
        self.newFrame.emit(self._frame)
        
        ### TODO: (Ignore for assignment 3)         ###
        ### Estimate the camera/marker pose         ###
        ### For example:                            ###
        
        #self._pose = tracker.estimatePose(self._frame)

        #and delete this:
        self._pose[2, 3] = (self._pose[2, 3] + 1)%100
        
        self.updateGL()

    def draw3DScene(self):
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(45.0, float(self.width())/float(self.height()), 0.1, 1000.0)
        # Better: glMultMatrixd(tracker.getProjectionMatrix().T)
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0)

        # use the etimated pose for model transformation
        glMultMatrixd(self._pose.T)

        # draw simple coordinate axes
        glBegin(GL_LINES)
        glColor3d(1.0,0.0,0.0)
        glVertex3d(0.0, 0.0, 0.0)
        glVertex3d(10.0, 0.0, 0.0)
        glColor3d(0.0,1.0,0.0)
        glVertex3d(0.0, 0.0, 0.0)
        glVertex3d(0.0, 10.0, 0.0)
        glColor3d(0.0, 0.0, 1.0)
        glVertex3d(0.0, 0.0, 0.0)
        glVertex3d(0.0, 0.0, 10.0)
        glEnd()

        # draw teapot
        glEnable(GL_LIGHTING)
        glPushMatrix()
        glTranslate(0.0, 0.0, 1.0)
        glRotate(90.0, 1.0, 0.0, 0.0)
        glutSolidTeapot(1)
        glPopMatrix()
        glDisable(GL_LIGHTING)


    def drawFrame(self):
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0.0, self.width(), self.height(), 0.0, -1.0, 1.0);
        glMatrixMode(GL_MODELVIEW);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glLoadIdentity();

        # convert the numpy array to an opengl texture
        glTexImage2D(GL_TEXTURE_2D, 0, 3, self._frame.shape[1], self._frame.shape[0], 0, GL_BGR, GL_UNSIGNED_BYTE, self._frame.tostring());
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
            
        glDisable(GL_DEPTH_TEST);

        glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
        glColor3d(1.0,1.0,1.0);

        # draw the frame mapped to a textured quad
        glEnable(GL_TEXTURE_2D);
        glBegin(GL_QUADS);
        glTexCoord2f( 0.0, 0.0);
        glVertex3f( 0, 0, 0 );
        
        glTexCoord2f( 1.0, 0.0 );
        glVertex3f( self.width(), 0, 0 );
        
        glTexCoord2f( 1.0, 1.0 );
        glVertex3f( self.width(), self.height(), 0 );
        
        glTexCoord2f( 0.0, 1.0 );
        glVertex3f( 0, self.height(), 0 );
        glEnd();
        glDisable(GL_TEXTURE_2D);
            
        glEnable(GL_DEPTH_TEST);
    

    def changeEvent(self, e):
        if e.type() == QtCore.QEvent.EnabledChange:
            if self.isEnabled():
                self._cameraDevice.newFrame.connect(self._onNewFrame)
            else:
                self._cameraDevice.newFrame.disconnect(self._onNewFrame)

class MyMainWindow(QtGui.QWidget):

    
    def __init__(self):
        self.boxframe = None
        QtGui.QWidget.__init__(self, None)
        self.setWindowTitle('Simple AR Display')
        
        # specify layout
        hbox = QtGui.QVBoxLayout(self)
        hbox.setDirection(QtGui.QBoxLayout.TopToBottom)

        mbox = QtGui.QWidget()
        
        vbox = QtGui.QVBoxLayout(mbox)
        vbox.setDirection(QtGui.QBoxLayout.LeftToRight)        
                
     
        # get camera device
        self.cameraDevice = CameraDevice(mirrored=True)
        self.cameraDevice.newFrame.connect(self.onNewCameraFrame)

        # add slider to control horizontal position
        self.w, self.h = self.cameraDevice.frameSize
        self.horizontalPositionSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
        self.horizontalPositionSlider.setRange(1,self.w)
        self.horizontalPositionSlider.setSingleStep(2);
        self.horizontalPositionSlider.valueChanged[int].connect(self.horizontalPositionSlidervalue)
        self.horizontalPositionSlider.setValue(self.w/2)
        hbox.addWidget(self.horizontalPositionSlider)        

        hbox.addWidget(mbox)
        
        # add widget to show the augmented video input image
        arWidget = ARWidget(self.cameraDevice)
        arWidget.newFrame.connect(self.onNewCameraFrame)
        vbox.addWidget(arWidget)
        
        # add slider to control vertical position
        self.w, self.h = self.cameraDevice.frameSize
        self.verticalPositionSlider = QtGui.QSlider(QtCore.Qt.Vertical)
        self.verticalPositionSlider.setRange(1,self.h)
        self.verticalPositionSlider.setSingleStep(2);
        self.verticalPositionSlider.valueChanged[int].connect(self.verticalPositionSlidervalue)
        self.verticalPositionSlider.setValue(self.h/2)
        vbox.addWidget(self.verticalPositionSlider)        

        # add slider to control size of box
        self.w, self.h = self.cameraDevice.frameSize
        self.sizePositionSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
        self.sizePositionSlider.setRange(1,self.h)
        self.sizePositionSlider.setSingleStep(2);
        self.sizePositionSlider.valueChanged[int].connect(self.sizePositionSlidervalue)
        self.sizePositionSlider.setValue(self.h/4)
        hbox.addWidget(self.sizePositionSlider)

        # add buttonRoi to control
        self.buttonRoiValue = False
        self.buttonRoi = QtGui.QPushButton("Roi", self)
        self.buttonRoi.clicked.connect(self.handleButtonRoi)
        hbox.addWidget(self.buttonRoi)
        
        # add buttonGray to control
        self.buttonGrayValue = False
        self.buttonGray = QtGui.QPushButton("Gray", self)
        self.buttonGray.clicked.connect(self.handleButtonGray)
        hbox.addWidget(self.buttonGray)

        # add buttonHist to control
        self.buttonHistValue = False
        self.buttonHist = QtGui.QPushButton("Histogramm", self)
        self.buttonHist.clicked.connect(self.handleButtonHist)
        hbox.addWidget(self.buttonHist)

        # add buttonGS to control
        self.buttonGSValue = False
        self.buttonGS = QtGui.QPushButton("Grauwertspreitzun", self)
        self.buttonGS.clicked.connect(self.handleButtonGS)
        self.buttonGS.setEnabled(False)
        hbox.addWidget(self.buttonGS)

        # add buttonHistEg to control
        self.buttonHistEqValue = False
        self.buttonHistEq = QtGui.QPushButton("Histogram-Ausgleich", self)
        self.buttonHistEq.setEnabled(False)
        self.buttonHistEq.clicked.connect(self.handleButtonHistEq)
        hbox.addWidget(self.buttonHistEq)

        # add buttonBin to control
        self.buttonBinValue = False
        self.buttonBin = QtGui.QPushButton("Binarisierung", self)
        self.buttonBin.setEnabled(False)
        self.buttonBin.clicked.connect(self.handleButtonBin)
        hbox.addWidget(self.buttonBin)

        # add slider to control Binarisierung
        self.binSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
        self.binSlider.setRange(1,self.h)
        self.binSlider.setSingleStep(0.1);
        self.binSlider.valueChanged[int].connect(self.binSlidervalue)
        self.binSlider.setValue(self.h/2)
        self.binSlider.setEnabled(False)
        hbox.addWidget(self.binSlider)
        
        # add buttonOts to control
        self.buttonOtsValue = False
        self.buttonOts = QtGui.QPushButton("Otsu", self)
        self.buttonOts.setEnabled(False)
        self.buttonOts.clicked.connect(self.handleButtonOts)
        hbox.addWidget(self.buttonOts)        
    
    def verticalPositionSlidervalue(self, value):
        self.verticalPositionvalue = value

    def handleButtonRoi(self):
        self.buttonRoiValue = not self.buttonRoiValue
        
    def handleButtonGray(self):
        self.buttonGrayValue = not self.buttonGrayValue
        self.buttonGS.setEnabled(self.buttonGrayValue)
        self.buttonHistEq.setEnabled(self.buttonGrayValue)
        self.buttonBin.setEnabled(self.buttonGrayValue)
        self.binSlider.setEnabled(self.buttonGrayValue)
        self.buttonOts.setEnabled(self.buttonGrayValue)

    def handleButtonHist(self):
        self.buttonHistValue = not self.buttonHistValue

    def handleButtonGS(self):
        self.buttonGSValue = not self.buttonGSValue

    def handleButtonHistEq(self):
        self.buttonHistEqValue = not self.buttonHistEqValue

    def handleButtonBin(self):
        self.buttonBinValue = not self.buttonBinValue

    def binSlidervalue(self, value):
        self.binValue = value

    def handleButtonOts(self):
        self.buttonOtsValue = not self.buttonOtsValue

    def horizontalPositionSlidervalue(self, value):
        self.horizontalPositionvalue = value

    def sizePositionSlidervalue(self, value):
        self.sizePositionvalue = value

    def hist_lines(self, im, mask, size):
        h = np.zeros((size,size,3))
        if len(im.shape)!=2:
            im = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
        hist_item = cv2.calcHist([im],[0],mask,[256],[0,255])
        cv2.normalize(hist_item,hist_item,0,255,cv2.NORM_MINMAX)
        hist=np.int32(np.around(hist_item))
        for x,y in enumerate(hist):
           cv2.line(h,(x,0),(x,y),(255,255,255))
        y = np.flipud(h)
        return y
    
    def onNewCameraFrame(self, frame):
        # set vertical position of rectangle
        verticalPosition = int(self.verticalPositionvalue)
        

        ### TODO:                                    ###
        ### 1. set horizontal position of box        ###
        horizontalPosition = int(self.horizontalPositionvalue)

        ### 2. set size of box                       ###
        sizePosition = int(self.sizePositionvalue)

        x1 = horizontalPosition-sizePosition/2
        y1 = self.h-verticalPosition-sizePosition/2        
        x2 = horizontalPosition+sizePosition/2
        y2 = self.h-verticalPosition+sizePosition/2

        #cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 0, 0))
        
        ### 3. Inverting image region in ROI defined ###
        ###    by the box                            ###

        if self.buttonRoiValue:
            frame[y1:y2, x1:x2] = cv2.bitwise_not(frame[y1:y2, x1:x2])
            
        frameCopy = frame.copy()
                
        if self.buttonGrayValue:
            frameCopy = cv2.cvtColor(frameCopy, cv2.COLOR_BGR2GRAY)          

            if self.buttonGSValue:
                gMin = frameCopy.min()
                gMax = frameCopy.max()
                            
                frameCopy[:] = (frameCopy[:]-gMin)*255.0/(gMax-gMin)

            if self.buttonHistEqValue:
                frameCopy = cv2.equalizeHist(frameCopy)

            if self.buttonBinValue:
                binValue = int(self.binValue)
                ret,frameCopy = cv2.threshold(frameCopy,binValue,255,cv2.THRESH_BINARY)

            if self.buttonOtsValue:
                ret2,frameCopy = cv2.threshold(frameCopy,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
                        
            frameCopy = cv2.cvtColor(frameCopy, cv2.COLOR_GRAY2BGR)
            
        else:
            self.buttonGSValue = False
            self.buttonHistEqValue = False
            self.buttonBinValue = False
            self.buttonOtsValue = False
            
            

        if self.buttonHistValue:
            ###
            size = self.w / 3
            am = self.hist_lines(frameCopy,None, size)
            frameCopy[0 : size, 0: size] = histogram
            ###


            '''
            size = 120

            h = np.zeros((size, size, 3))
            bins = np.arange(size).reshape(size, 1)
            
            hist_item = cv2.calcHist([frameCopy],[0],None,[size],[0,256])
            cv2.normalize(hist_item, hist_item, 0, 255, cv2.NORM_MINMAX)
            hist = np.int32(np.around(hist_item))
            pts = np.column_stack((bins, hist))
            cv2.polylines(h, [pts], False, (255,255,255))     
            
            h = np.flipud(h)
            frameCopy[0:size, 0:size] = h  '''     
            
        
        if self.boxframe is not None:
            frame[:] = self.boxframe
            
        self.boxframe = frameCopy

if __name__ == "__main__":
    glutInit(sys.argv) # don't need this under Mac OS X
    app = QtGui.QApplication(sys.argv)
    w = MyMainWindow()
    w.show()
    
    sys.exit(app.exec_())


